/*
 *
 * The DbUnit Database Testing Framework
 * Copyright (C)2002-2004, DbUnit.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package org.fixies.database.dbunit;

import junit.framework.Assert;
import org.dbunit.DatabaseUnitException;
import org.dbunit.dataset.*;
import org.dbunit.dataset.datatype.DataType;
import org.dbunit.dataset.datatype.UnknownDataType;
import org.fixies.utils.UnexpectedProblemException;

import java.util.Arrays;
import java.util.Comparator;

/**
 * @author Manuel Laflamme
 * @version $Revision: 1.5 $
 * @since Mar 22, 2002
 */
public final class DBAssertion {

    private static final ColumnComparator COLUMN_COMPARATOR = new ColumnComparator();

    private DBAssertion() {
    }

    /**
     * Asserts that the two specified dataset are equals. This method ignore the tables order.
     */
    public static void assertDataSetsEqual(IDataSet expectedDataSet, IDataSet actualDataSet)
            throws DatabaseUnitException {

        org.dbunit.Assertion.assertEquals(expectedDataSet, actualDataSet);
    }

    /**
     * ２つのテーブルが等しい事を表明する。
     *
     * @see org.dbunit.Assertion.assertEquals(ITable, ITable)
     */
    public static void assertTablesEqual(ITable expectedTable, ITable actualTable) {

        try {
            org.dbunit.Assertion.assertEquals(expectedTable, actualTable);
        } catch (DatabaseUnitException e) {
            throw new UnexpectedProblemException(e);
        }
    }

    public static void assertContains(IDataSet expectedDataSet, IDataSet actualDataSet) {
        // do not continue if same instance
        if (expectedDataSet == actualDataSet) {
            return;
        }
        try {
            String[] expectedNames = getSortedUpperTableNames(expectedDataSet);
            String[] actualNames = getSortedUpperTableNames(actualDataSet);

            // tables count
            Assert.assertTrue("DBのテーブルに不足があります。", expectedNames.length <= actualNames.length);

            // tables
            for (String name : expectedNames) {
                assertContains(expectedDataSet.getTable(name), actualDataSet.getTable(name));
            }
        } catch (DatabaseUnitException e) {
            throw new UnexpectedProblemException(e);
        }
    }

    /**
     * 期待するテーブルの内容が、実際のテーブルの内容を含む事を表明する。川崎健 レコードの順序は無視される。
     *
     * @param expectedTable 期待するテーブル
     * @param actualTable   実際のテーブル
     * @throws DataSetException
     * @throws DatabaseUnitException
     */
    public static void assertContains(ITable expectedTable, ITable actualTable) {

        try {
            assertContainsStrictOrder(new SortedTable(expectedTable), new SortedTable(actualTable,
                    expectedTable.getTableMetaData()));
        } catch (DataSetException e) {
            throw new UnexpectedProblemException(e);
        } catch (DatabaseUnitException e) {
            throw new UnexpectedProblemException(e);
        }
    }

    /**
     * 期待するテーブルの内容が、実際のテーブルの内容を含む事を表明する。レコードの順序を厳密に比較する。
     *
     * @param expectedTable 期待するテーブル
     * @param actualTable   実際のテーブル
     */
    public static void assertContainsStrictOrder(ITable expectedTable, ITable actualTable)
            throws DatabaseUnitException {
        // Do not continue if same instance
        if (expectedTable == actualTable) {
            return;
        }

        ITableMetaData expectedMetaData = expectedTable.getTableMetaData();
        ITableMetaData actualMetaData = actualTable.getTableMetaData();
        String expectedTableName = expectedMetaData.getTableName();

        // // verify table name
        // Assert.assertEquals("table name", expectedMetaData.getTableName(),
        // actualMetaData.getTableName());

        // Verify row count
        Assert.assertEquals("row count (table=" + expectedTableName + ")",
                expectedTable.getRowCount(), actualTable.getRowCount());

        Column[] expectedColumns = getSortedColumns(expectedMetaData);
        Column[] actualColumns = getSortedColumns(actualMetaData);

        // values as strings
        for (int i = 0; i < expectedTable.getRowCount(); i++) {

            for (int j = 0, k = 0; j < expectedColumns.length; j++, k++) {

                while (!isColumnNameEquals(expectedColumns[j], actualColumns[k])) {

                    // 実際のカラムが最後のカラムの場合、テスト失敗
                    if (k >= actualColumns.length) {
                        Assert.fail("カラム:" + expectedColumns[i].getColumnName() + "は存在しません。");
                    }

                    // 実際のカラムを次に進める
                    k++;
                }
                // 同じカラムを取り出す
                Column expectedColumn = expectedColumns[j];
                Column actualColumn = actualColumns[k];
                // カラム名を取り出す
                String expectedColumnName = expectedColumn.getColumnName();

                // 期待するテーブルの値を取り出す
                Object expectedValue = expectedTable.getValue(i, expectedColumnName);
                // 実際のテーブルの値を取り出す
                Object actualValue = actualTable.getValue(i, expectedColumnName);

                // 比較
                DataType dataType = getComparisonDataType(expectedTableName, expectedColumn,
                        actualColumn);
                if (dataType.compare(expectedValue, actualValue) != 0) {
                    Assert.fail("value (table=" + expectedTableName + ", " + "row=" + i + ", col="
                            + expectedColumnName + "): expected:<" + expectedValue + "> but was:<"
                            + actualValue + ">");
                }
            }
        }
    }

    static DataType getComparisonDataType(String tableName, Column expectedColumn,
                                          Column actualColumn) {

        DataType expectedDataType = expectedColumn.getDataType();
        DataType actualDataType = actualColumn.getDataType();

        // The two columns have different data type
        if (!expectedDataType.getClass().isInstance(actualDataType)) {
            // Expected column data type is unknown, use actual column data type
            if (expectedDataType instanceof UnknownDataType) {
                return actualDataType;
            }

            // Actual column data type is unknown, use expected column data type
            if (actualDataType instanceof UnknownDataType) {
                return expectedDataType;
            }

            // Impossible to determine which data type to use
            Assert.fail("Incompatible data types: " + expectedDataType + ", " + actualDataType
                    + " (table=" + tableName + ", col=" + expectedColumn.getColumnName() + ")");
        }
        // // Both columns have unknown data type, use string comparison
        // else if (expectedDataType instanceof UnknownDataType)
        // {
        // return DataType.LONGVARCHAR;
        // }

        // Both columns have same data type, return any one of them
        return expectedDataType;
    }

    private static boolean isColumnNameEquals(Column expClm, Column actClm) {
        String expectedColumnName = expClm.getColumnName();
        String actualColumnName = actClm.getColumnName();
        return expectedColumnName.equalsIgnoreCase(actualColumnName);
    }

    private static Column[] getSortedColumns(ITableMetaData metaData) throws DataSetException {
        Column[] columns = metaData.getColumns();
        Column[] sortColumns = new Column[columns.length];
        System.arraycopy(columns, 0, sortColumns, 0, columns.length);
        Arrays.sort(sortColumns, COLUMN_COMPARATOR);
        return sortColumns;
    }

    private static String[] getSortedUpperTableNames(IDataSet dataSet) throws DataSetException {
        String[] names = dataSet.getTableNames();
        for (int i = 0; i < names.length; i++) {
            names[i] = names[i].toUpperCase();
        }
        Arrays.sort(names);
        return names;
    }

    // //////////////////////////////////////////////////////////////////////////
    // ColumnComparator class

    private static class ColumnComparator implements Comparator<Column> {
        public int compare(Column column1, Column column2) {
            String columnName1 = column1.getColumnName();
            String columnName2 = column2.getColumnName();
            return columnName1.compareToIgnoreCase(columnName2);
        }
    }
}
